#pragma once
#include <3rdParty/getopt.h>
#include "../common.hpp"
#include "Singleton.hpp"
#include "Global.hpp"

namespace YAML {
class Node;
}
namespace zzz{
typedef enum {CMDPARSER_NOARG=0,CMDPARSER_OPTARG=1,CMDPARSER_ONEARG=2} ArgRequirement;
class CmdParser
{
public:
  ~CmdParser();
  void AddShortOption(char shortopt, ArgRequirement re, const string &desc, const string &file);
  bool Parse(const string &usage, int argc, char *argv[], const char* currentfile);
  vector<string> sequential_;
private:
  void Help(const string &usage, const char *currentfile);
  void HelpLong(const string &usage);

  void AddAutoOption(const string &longopt, const string &desc, const string &file, const char shortopt, bool is_switch = false);
  typedef void (*PostParse)(void);
  void AddPostParseFunc(PostParse post);
  void AddLongOption(const string &longopt, ArgRequirement re, int shortopt, const string &desc, const string &file);
  bool ParseShortOption(const int c, const char *optarg);
  bool ParseLongOption(const string &name, const char *optarg);

  void LoadOptionFile();
  void LoadOptionProfile(const YAML::Node *p_node);

  vector<option> long_options;
  vector<string> option_file;
  vector<zuint> option_file_n;
  string short_options;

  vector<string> optstr,optdesc;

  map<int, string> short_long_;
  map<int, string> neg_short_long_;

  vector<PostParse> postparse_;

  template<typename T> friend class FlagsHelper;
  friend class FlagsSwitchHelper;
  friend class FlagsPostHelper;
};

#define ZSHORTFLAGS(shortopt, arg, desc) \
  zzz::Singleton<zzz::CmdParser>::Instance().AddShortOption(shortopt,arg,desc,__FILE__);

#define ZCMDSEQ (zzz::Singleton<zzz::CmdParser>::Instance().sequential_)

// Make it a better name, should always call it first.
#define ZINIT(usage) \
  ZGLOBAL_ADD(std::string(__DATE__) + ", " + __TIME__, "__MAIN_TIMESTAMP__");\
  zzz::Singleton<zzz::CmdParser>::Instance().Parse(usage,argc,argv,__FILE__);

#define ZCMDPARSE(usage) ZINIT(usage)

template<typename T>
class FlagsHelper {
public:
  FlagsHelper(const string &name ,T *v, const string &desc, const string &file, const char x=0) {
    string strvar("ZFLAG_");
    strvar += name;
    ZGLOBAL_ADD(v, strvar);
    Singleton<CmdParser>::Instance().AddAutoOption(name, desc, file, x);
  }
};

class FlagsSwitchHelper {
public:
  FlagsSwitchHelper(const string &name ,bool *v, const string &desc, const string &file, const char x=0) {
    string strvar("ZFLAG_");
    strvar += name;
    ZGLOBAL_ADD(v, strvar);
    Singleton<CmdParser>::Instance().AddAutoOption(name, desc, file, x, true);
  }
};

#define ZFLAGS_DOUBLE(longopt,default_value, desc) \
  double ZFLAG_##longopt = default_value; \
  namespace ZFLAGS_NAMESPACE{zzz::FlagsHelper<double> __ZFLAG_##longopt##_HELPER__(#longopt, &ZFLAG_##longopt, desc,__FILE__);}
#define ZFLAGS_DOUBLE2(longopt,default_value, desc, x) \
  double ZFLAG_##longopt = default_value; \
  namespace ZFLAGS_NAMESPACE{zzz::FlagsHelper<double> __ZFLAG_##longopt##_HELPER__(#longopt, &ZFLAG_##longopt, desc,__FILE__, x);}

#define ZFLAGS_INT(longopt,default_value, desc) \
  int ZFLAG_##longopt = default_value; \
  namespace ZFLAGS_NAMESPACE{zzz::FlagsHelper<int> __ZFLAG_##longopt##_HELPER__(#longopt, &ZFLAG_##longopt, desc,__FILE__);}
#define ZFLAGS_INT2(longopt,default_value, desc, x) \
  int ZFLAG_##longopt = default_value; \
  namespace ZFLAGS_NAMESPACE{zzz::FlagsHelper<int> __ZFLAG_##longopt##_HELPER__(#longopt, &ZFLAG_##longopt, desc,__FILE__, x);}

#define ZFLAGS_BOOL(longopt,default_value, desc) \
  bool ZFLAG_##longopt = default_value; \
  namespace ZFLAGS_NAMESPACE{zzz::FlagsHelper<bool> __ZFLAG_##longopt##_HELPER__(#longopt, &ZFLAG_##longopt, desc,__FILE__);}
#define ZFLAGS_BOOL2(longopt,default_value, desc, x) \
  bool ZFLAG_##longopt = default_value; \
  namespace ZFLAGS_NAMESPACE{zzz::FlagsHelper<bool> __ZFLAG_##longopt##_HELPER__(#longopt, &ZFLAG_##longopt, desc,__FILE__, x);}

#define ZFLAGS_STRING(longopt,default_value, desc) \
  std::string ZFLAG_##longopt = default_value; \
  namespace ZFLAGS_NAMESPACE{zzz::FlagsHelper<std::string> __ZFLAGS_##longopt##_HELPER___(#longopt, &ZFLAG_##longopt, desc,__FILE__);}
#define ZFLAGS_STRING2(longopt,default_value, desc, x) \
  std::string ZFLAG_##longopt = default_value; \
  namespace ZFLAGS_NAMESPACE{zzz::FlagsHelper<std::string> __ZFLAG_##longopt##_HELPER___(#longopt, &ZFLAG_##longopt, desc,__FILE__, x);}

#define ZFLAGS_SWITCH(longopt, desc) \
  bool ZFLAG_##longopt = false; \
  namespace ZFLAGS_NAMESPACE{zzz::FlagsSwitchHelper __ZFLAG_##longopt##_HELPER___(#longopt, &ZFLAG_##longopt, desc,__FILE__);}
#define ZFLAGS_SWITCH2(longopt, desc, x) \
  bool ZFLAG_##longopt = false; \
  namespace ZFLAGS_NAMESPACE{zzz::FlagsSwitchHelper __ZFLAG_##longopt##_HELPER___(#longopt, &ZFLAG_##longopt, desc,__FILE__, x);}

#define ZFLAGS_MULTIPLE(longopt, desc) \
  std::vector<std::string> ZFLAG_##longopt; \
  namespace ZFLAGS_NAMESPACE{zzz::FlagsHelper<std::vector<std::string> > __ZFLAG_##longopt##_HELPER__(#longopt, &ZFLAG_##longopt, desc,__FILE__);}
#define ZFLAGS_MULTIPLE2(longopt, desc, x) \
  std::vector<std::string> ZFLAG_##longopt; \
  namespace ZFLAGS_NAMESPACE{zzz::FlagsHelper<std::vector<std::string> > __ZFLAG_##longopt##_HELPER__(#longopt, &ZFLAG_##longopt, desc,__FILE__, x);}

class FlagsPostHelper {
public:
  FlagsPostHelper(CmdParser::PostParse post) {
    Singleton<CmdParser>::Instance().AddPostParseFunc(post);
  }
};
#define ZFLAGS_POST(post) \
  namespace ZFLAGS_NAMESPACE{zzz::FlagsPostHelper __FLAGS_##post##_HELPER__(post);}

#define ZFLAG_GET(type, name) (*ZGLOBAL_GET(type*, name))
}
